package org.oep.jmingle;
import org.oep.widgets.EZText;
import org.oep.widgets.MonologueWidget;
import org.oep.widgets.SpeechBubble;
import org.oep.widgets.TextWidget;
import org.oep.bluetooth.*;
import org.oep.game.Actor;
import org.oep.game.Camera;
import org.oep.game.ImageStore;
import org.oep.widgets.ChoiceWidget;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.game.TiledLayer;
import javax.microedition.lcdui.game.LayerManager;

public class Field extends GameCanvas implements Runnable, BluetoothClient {
	private Minglet player;
	private Sprite loadingIcon;
	protected BTHandler bt = new BTHandler();
	private MingleTracker tracker;
	private Thread gameThread;
	
	protected Hashtable minglets = new Hashtable();
	
	private Vector newGuys = new Vector();
	private Vector inhabitants = new Vector();
	private Vector exuent = new Vector();
	private Vector paintBuffer = new Vector();
	
	private final ChoiceWidget choiceMenu = new ChoiceWidget(ChoiceWidget.TYPE_BAR);
	
	private Minglet mingleSelection;
	private int actionChoice;
	
	private Scripting scripting;
	private TiledLayer background;
	private LayerManager viewfinder;
	private Camera camera = new Camera();
	private MonologueWidget monologueBox;
	protected MonologueWidget tutorialBox;
	private IDWidget idBox;
	private EventPipe eventHandler;
	private TextWidget marquee;
	
	private boolean done = false;
	private boolean searching = false;
	private boolean menuShowsInhabitants = false;
	private boolean menuShowsCollisions = false;
	private boolean menuShowsMain = false;
	private boolean fireDown = false; // Prevents repeated FIRE presses
	private boolean menuCycle = false; // Prevents repeated DIRECTION presses on choice menu
	private boolean playerUsedMenu = false;
	private boolean isTutorial = false;
	
	protected int
		BOUNDARY_TOP = 0,
		BOUNDARY_BOTTOM = getHeight(),
		BOUNDARY_LEFT = 0,
		BOUNDARY_RIGHT = getWidth();
	
	private int fallOffset = 0;
	private int fallVelocity = 0;
	
	private long lastAction = System.currentTimeMillis();
	
	private int CHOICE_MINGLE, CHOICE_INFO,
		CHOICE_CANCEL, CHOICE_EXIT, CHOICE_SCAN, CHOICE_BROWSE, CHOICE_STOPSCAN, CHOICE_BOOK;
		
	
	public Field(MingleTracker t) {
		super(true);
		tracker = t;
		construct();
	}
	
	public void run() {
		long start, end;
		int waitTime;
		
		while(!done) {
			start = System.currentTimeMillis();
			
			if(isTutorial && scripting == null) {
				scripting = Scripting.createEnvironment(this, JMingle.TUTORIAL_SCRIPT);
				scripting.eval();
			}
			
			if(player != null) {
				if(isTutorial) {
					checkTutorialInput();
				}
				else {
					checkUserInput(); 
					gameLogic();
				}
				updateGameScreen(getGraphics());
			}
			else {
				errorScreen(getGraphics());
			}
			
			end = System.currentTimeMillis();
			
			// We pause for a second over the FPS
			waitTime = (1000 / 15) - (int) (end - start);
			
			if(waitTime > 0)
				try { Thread.sleep( waitTime );	}
				catch(InterruptedException e) {	System.err.println(e); }	
		}
		
		if(eventHandler != null)
			eventHandler.eventReceived(JMingle.SHOW_MAIN);
	}
	
/* ------------------------------------------------------------------------------
 * 
 * Game logic functions
 * 
 * ------------------------------------------------------------------------------ */
	
	private void gameLogic() {
		doAI();
		setCamera();
	}
	
	private void setCamera() {
		Minglet subject;
		if(menuShowsInhabitants || choiceMenu.active() && menuShowsCollisions && choiceMenu.getChoiceIndex() != CHOICE_CANCEL) {
			subject = (Minglet) choiceMenu.getChoiceObject();
		}
		else {
			subject = player;
		}
		
		
 		int camX = Math.min(
				BOUNDARY_RIGHT - getWidth() / 2,
				Math.max(subject.getX(), getWidth() / 2)
				);
		camera.setSubjectPosition(camX, subject.getY());
		camera.interpolate();
	}
	
	private void doAI() {
		//
		// This loop is for the normal AI:
		// Amble about the play area and mingle
		//
		for(int i = 0; i < inhabitants.size(); i++) {
			Minglet m = (Minglet) inhabitants.elementAt(i);
			if(m.isUntasked()) {
				// If we couldn't get a target or by some 1 in N chance, go cry about it.
				if(JMingle.RANDOM.nextInt(2) == 0) {
					int rectWidth = (BOUNDARY_RIGHT - BOUNDARY_LEFT) - player.getWidth();
					int rectHeight = (BOUNDARY_BOTTOM - BOUNDARY_TOP);
					m.goTo(BOUNDARY_LEFT + player.getWidth() / 2 +  JMingle.RANDOM.nextInt(rectWidth),
							BOUNDARY_TOP + JMingle.RANDOM.nextInt(rectHeight));
				}
				else {
					m.setTarget( pickMinglet(m) );
				}
			}
			m.doAI();
		}
		
		//
		// This loop is for the exiting minglets
		// Go out of the play area and die
		//
		for(int i = 0; i < exuent.size(); i++) {
			Minglet m = (Minglet) exuent.elementAt(i);
			// The Minglet has left the game boundaries
			if(m.isUntasked() && (m.getX() < BOUNDARY_LEFT || m.getX() > BOUNDARY_RIGHT)) {
				exuent.removeElementAt(i);
				i--;
			}
			// We'd like the Minglet to move toward the end boundaries if he is untasked
			else if(m.isUntasked()) {
				m.interrupt();
				if(m.getX() <= (BOUNDARY_RIGHT - BOUNDARY_LEFT) / 2) {
					m.goTo(BOUNDARY_LEFT - m.getWidth(), m.getY());
				}
				else {
					m.goTo(BOUNDARY_RIGHT + m.getWidth(), m.getY());
				}
			}
			m.doAI();
		}
		
		//
		// This loop is for the 'falling' minglets
		//
		if(newGuys.size() > 0) {
			fallVelocity++;
			fallOffset += fallVelocity;
		}
		for(int i = 0; i < newGuys.size(); i++) {
			Minglet m = (Minglet) newGuys.elementAt(i);
			if(fallOffset >= m.getY() && m.getFrame() < m.getFrameSequenceLength() - 2) {
				m.nextFrame();
			}
			else if(fallOffset >= m.getY()) {
				// We played the animation, now it's time to become an inhabitant
				newGuys.removeElementAt(i); i--;
				m.setAnimationState(Actor.KEY_STAND);
				m.setCollidable(true);
				inhabitants.addElement(m);
			}
		}
		
		
		/*
		 * Quickly -- if the player's target isn't near him anymore, stop the mingling
		 * animation.
		 */
		
		// TODO: Write this code
	}
	
	private Minglet pickMinglet(Minglet loner) {
		if(inhabitants.size() == 1) return player;
		
		Minglet choice = null;
		
		while(choice == null || choice == loner) {
			int r = JMingle.RANDOM.nextInt(inhabitants.size() + 1);
			if(r < inhabitants.size()) {
				choice = (Minglet) inhabitants.elementAt(r);
			}
			else {
				choice = player;
			}
		}
		
		return choice;
	}
	
	
/* ------------------------------------------------------------------------------
 * 
 * User input functions
 * 
 * ------------------------------------------------------------------------------ */
	
	private void checkUserInput() {
		int keyStates = this.getKeyStates();
		
		if(keyStates != 0) lastAction = System.currentTimeMillis();
		
		// Initially, we process things that are only handled by the FIRE key
		if((keyStates & FIRE_PRESSED) != 0 && !fireDown) {
			handleFire();
			playerUsedMenu = true;
			fireDown = true;
		} else if((keyStates & FIRE_PRESSED) == 0)
			fireDown = false;
		
		
		// These couple of things rely on more than FIRE, so we pass the keyStates
		if(choiceMenu.active()) {
			updateMenuState(keyStates);
		}
		else if(idBox.isActive()) {
			updateIDBoxState(keyStates);
		}
		// Do not update player position if a widget is active
		else if(!idBox.isActive() && !monologueBox.isActive()) {
			updatePlayerPosition(keyStates);
		}
		

	}
	
	private void checkTutorialInput() {
		int keyStates = this.getKeyStates();
		
		if((keyStates & FIRE_PRESSED) != 0 && !fireDown) {
			handleFire();
			fireDown = true;
		}
		else if((keyStates & FIRE_PRESSED) == 0){
			fireDown = false;
		}
	}
	
	private void handleFire() {
		// Tutorial box absorbs it first...
		if(isTutorial && tutorialBox.isActive()) {
			tutorialBox.action();
			
			// See if the dialogue was inactivated and auto-progress
			if(!tutorialBox.isActive() && scripting.isWaiting()) {
				scripting.eval();
			}
			return;
		}

		if(isTutorial && scripting.isWaiting()) {
			scripting.eval();
			return;
		}
		
		if(isTutorial && eventHandler != null) {
			eventHandler.eventReceived(JMingle.SHOW_MAIN);
			return;
		}
		
		// REMAINDER: GAMEPLAY CODE //
		
		// Next, monologueBox
		if(monologueBox.isActive()) {
			monologueBox.action();
			return;
		}
		
		// Next, the infowidget will absorb it
		if(idBox.isActive()) {
			idBox.setActive(false);
			return;
		}
		
		// Otherwise, it's the choicewidget		
		if(!choiceMenu.active()) {
			
			// In this case, the player has no previous actions. We handle this in two ways
			// 1. See who the player is touching and offer to mingle take an action with them
			// 2. If they aren't touching anyone, just present the main menu
			// This is all handled by buildMenuFromCollisions()
			buildMainMenu();
			
		} else if(menuShowsMain) {
			
			// The user was looking at the main menu previously. Just handle
			// the action the player selected
			
			System.out.println("We came form the main menu");
			
			if(!searching) System.out.println("Not searching");
			if(choiceMenu.getChoiceIndex() == CHOICE_SCAN) System.out.println("Chose to scan");
			
			if(playerIsColliding() && (choiceMenu.getChoiceIndex() == CHOICE_MINGLE || choiceMenu.getChoiceIndex() == CHOICE_INFO)) {
				actionChoice = choiceMenu.getChoiceIndex();
				buildMenuFromCollisions();
			}
			else if(!searching && choiceMenu.getChoiceIndex() == CHOICE_SCAN) {
				this.newArea();
				choiceMenu.clear();
			}
			else if(searching && choiceMenu.getChoiceIndex() == CHOICE_STOPSCAN) {
				this.stopScan();
				choiceMenu.clear();
			}
			else if(choiceMenu.getChoiceIndex() == CHOICE_BOOK) {
				handleBrowseBox();
				choiceMenu.clear();
			}
			else if(choiceMenu.getChoiceIndex() == CHOICE_BROWSE) {
				buildMenuFromInhabitants();
				// This will make code in the next if-block not take any actions
				lastAction = -1;
			}
			else if(choiceMenu.getChoiceIndex() == CHOICE_EXIT) {
				choiceMenu.clear();
				done = true;
			}
			else {
				choiceMenu.clear();
			}
			
			menuShowsMain = false;
			
		} else if(menuShowsCollisions) {
			System.out.println("Menushowscollions()");
			
			// In this case, the player was choosing what action to take with
			// the previously selected Minglet. We just handle their selection
			
			if(choiceMenu.getChoiceIndex() == CHOICE_CANCEL) {
				// Do nothing
			}
			else if(actionChoice == CHOICE_MINGLE) {
				// We mingled with someone!
				mingleSelection = (Minglet) choiceMenu.getChoiceObject(); 
				handleMingle();
			}
			else if(actionChoice == CHOICE_INFO) {
				// Getting info about someone!
				mingleSelection = (Minglet) choiceMenu.getChoiceObject();
				handleIDBox();
			}
			
			menuShowsCollisions = false;
			mingleSelection = null;
			choiceMenu.clear();
		}
		else if(menuShowsInhabitants){
			choiceMenu.clear();
			menuShowsInhabitants = false;
		}
		
		System.out.println("Setting menu opts");
		
		// A catch-all way to customize our choice menu
		if(choiceMenu.active()) {
			choiceMenu.setDefaultFont(JMingle.DEFAULT_FONT);
			choiceMenu.setSelectedFont(JMingle.DEFAULT_FONT);
			choiceMenu.setDiameter( (player.getHeight() * 15) / 10  );
		}
		System.out.println("Done");
	}
	
	private void handleIDBox() {
		idBox.clear();
		idBox.addSubject(mingleSelection);
		idBox.setSource(IDWidget.SUBJECTS);
		idBox.setActive(true);
	}
	
	private void handleBrowseBox() {
		idBox.clear();
		idBox.setSource(IDWidget.TRACKER);
		idBox.setActive(true);
	}
	
	private void handleMingle() {
		long now = System.currentTimeMillis();
		long diff;
		long bonus = 0;
		int pct = tracker.getBonus();
		
		
		/*
		 * Some Mingle sequence would go here
		 */		
				
		// Update the marquee to reflect the bonus
		if(mingleSelection.getLastSeen() > 0) {
			diff = now - mingleSelection.getLastSeen();
			
			// Do we get a bonus? Plzplzplz....
			if(tracker.getFavoriteThingID(player.getAddress()) == tracker.getFavoriteThingID(mingleSelection.getAddress())) {
				bonus = diff * pct / 100;
			}
			
			tracker.addScore(diff + bonus);
			
			if(bonus == 0)
				marquee = new TextWidget(player.getX(), player.getY(), "+" + Utils.friendlyTimeDiff(diff), 0x00BBDD, JMingle.BOLD_FONT);
			else
				marquee = new TextWidget(player.getX(), player.getY(), "+" + Utils.friendlyTimeDiff(diff) + " +" + Utils.friendlyTimeDiff(bonus), 0x00AA00, JMingle.BOLD_FONT);
			
		} else {
			marquee = new TextWidget(player.getX(), player.getY(), "+NEW!", 0x0000AA, JMingle.BOLD_FONT);
		}
		marquee.setActive(true);
		marquee.setLife(50);
		
		// Remove the happy face if it was there
		mingleSelection.setMingled(true);
		
		// Force the two to mingle
		player.setMingling(true);
		mingleSelection.forceMingle(player);
		
		// Mark it in the datas
		mingleSelection.setLastSeen( now );
		tracker.doMingle(mingleSelection);
	}
	
	private void buildMenuFromCollisions() {
		System.out.println("ITS PARTY TAIME");
		menuShowsCollisions = true;
		menuShowsMain = false;
		choiceMenu.clear();
		
		for(int i = 0; i < inhabitants.size(); i++) {
			Minglet a = (Minglet) inhabitants.elementAt(i);
			if(a.collidesWith(player)) {
				choiceMenu.addChoice(a);
				System.out.println("Adding " + a);
			}
		}
	
		
		CHOICE_CANCEL = choiceMenu.addChoice("Cancel");
		
		choiceMenu.setActive(true);
	}
	
	private void buildMenuFromInhabitants() {
		menuShowsMain = false;
		choiceMenu.clear();
		
		if(inhabitants.size() == 0) {
			return;
		}
		
		menuShowsInhabitants = true;
		
		for(int i = 0; i < inhabitants.size(); i++) {
			Minglet a = (Minglet) inhabitants.elementAt(i);
			choiceMenu.addChoice(a);
		}
		
		choiceMenu.setActive(true);
	}
	
	private void buildMainMenu() {
		menuShowsCollisions = false;
		menuShowsMain = true;
		choiceMenu.clear();
		
		if(playerIsColliding()) {
			CHOICE_MINGLE = choiceMenu.addChoice("Mingle");
			CHOICE_INFO = choiceMenu.addChoice("Info");
		}
		else {
			// Deactivate these two choices
			CHOICE_MINGLE = -1;
			CHOICE_INFO = -1;
		}
		
		if(searching) CHOICE_STOPSCAN = choiceMenu.addChoice("Stop");
		else CHOICE_SCAN = choiceMenu.addChoice("Scan");
		
		CHOICE_BOOK = choiceMenu.addChoice("Minglebook");
		CHOICE_BROWSE = choiceMenu.addChoice("Browse");
		CHOICE_EXIT = choiceMenu.addChoice("Quit");
		CHOICE_CANCEL = choiceMenu.addChoice("Cancel");
		choiceMenu.setActive(true);
	}

	
	private void updateIDBoxState(int keyStates) {	
		if((keyStates & LEFT_PRESSED) != 0) {
			idBox.previous();
		}
		else if((keyStates & RIGHT_PRESSED) != 0) {
			idBox.next();
		}
	}
	
	private void updatePlayerPosition(int keyStates) {
		int x = player.getX();
		int y = player.getY();
		boolean stateSet = false;
		if((keyStates & UP_PRESSED) != 0) {
			y = Math.max(BOUNDARY_TOP, y - Minglet.SPEED);
			if(!stateSet) {
				stateSet = true;
				player.setAnimationState(Minglet.KEY_WALK_UP);
			}
			player.setMingling(false);
		}
		if((keyStates & DOWN_PRESSED) != 0) {
			y = Math.min(BOUNDARY_BOTTOM, y + Minglet.SPEED);
			if(!stateSet) {
				stateSet = true;
				player.setAnimationState(Minglet.KEY_WALK_DOWN);
			}
			player.setMingling(false);
		}
		if((keyStates & LEFT_PRESSED) != 0) {
			x = Math.max(BOUNDARY_LEFT + player.getWidth() / 2, x - Minglet.SPEED);
			if(!stateSet) {
				stateSet = true;
				player.setAnimationState(Minglet.KEY_WALK_LEFT);
			}
			player.setMingling(false);
		}
		if((keyStates & RIGHT_PRESSED) != 0) {
			x = Math.min(BOUNDARY_RIGHT - player.getWidth() / 2, x + Minglet.SPEED);
			if(!stateSet) {
				stateSet = true;
				player.setAnimationState(Minglet.KEY_WALK_RIGHT);
			}
			player.setMingling(false);
		}

		
		if(keyStates == 0)
			player.setAnimationState(Minglet.KEY_STAND);
		
		player.setPosition(x, y);
	}
	
	private void updateMenuState(int keyStates) {
		int DIRECTION_PRESSED = UP_PRESSED + DOWN_PRESSED + LEFT_PRESSED + RIGHT_PRESSED;
		
		if(!player.isPlaying(Minglet.KEY_STAND))
			player.setAnimationState(Minglet.KEY_STAND);
		
		if(!menuCycle && ((keyStates & RIGHT_PRESSED) != 0 || (keyStates & DOWN_PRESSED) != 0)) {
			choiceMenu.nextChoice();
		}
		
		else if(!menuCycle && ((keyStates & LEFT_PRESSED) != 0  || (keyStates & UP_PRESSED) != 0)) {
			choiceMenu.previousChoice();
		}
		
		if((keyStates & DIRECTION_PRESSED) == 0) {
			menuCycle = false;
		}
		else
			menuCycle = true;
	}
	
	
/* ------------------------------------------------------------------------------
 * 
 * Graphics functions
 * 
 * ------------------------------------------------------------------------------ */
	
	private void updateGameScreen(Graphics g) {
		g.setFont(JMingle.DEFAULT_FONT);
		g.setColor(0xFFFFFF);
		g.fillRect(0, 0, getWidth(), getHeight());
		
		viewfinder.setViewWindow(camera.getX() - getWidth() / 2, 0, getWidth(), getHeight());
		viewfinder.paint(g, 0, 0);
		
		paintMinglets();
		
		if(searching) {
			if(loadingIcon != null) {
				loadingIcon.nextFrame();
				loadingIcon.paint(g);
			}
		}
		
		
		StatusBar.paint(g, Utils.preciseTimeDiff(tracker.getScore()), player.getName(), marquee, tracker.getFavoriteThing(player.getAddress()));
		choiceMenu.setPosition(getWidth() / 2, StatusBar.getHeight() + choiceMenu.getHeight());
		choiceMenu.paint(g);
		
		monologueBox.tick();
		monologueBox.paint(g);

		tutorialBox.setPosition(getWidth() / 2 - tutorialBox.getWidth() / 2, StatusBar.getHeight() + choiceMenu.getHeight() + 3);
		tutorialBox.tick();
		tutorialBox.paint(g);
		
		idBox.paint(g);
		
		
		flushGraphics();
	}
	
	private void errorScreen(Graphics g) {
		g.setColor(0xFFFFFF);
		g.fillRect(0, 0, getWidth(), getHeight());
		
		g.setColor(0);
		EZText.paint(g, "Could not initialize Bluetooth subsystem. Make sure your device has Bluetooth turned on!",
				JMingle.DEFAULT_FONT, getWidth() / 2, getHeight() / 2, 3 * getWidth() / 4);
		
		done = getKeyStates() != 0;
		
		flushGraphics();
	}
	
	private void paintMinglets() {
		paintBuffer.removeAllElements();

		// Order the inhabitants with descending y-coordinates in our buffer
		if(!isTutorial) {
			addToPaintBuffer(player);
			addToPaintBuffer(newGuys);
			addToPaintBuffer(inhabitants);
			addToPaintBuffer(exuent);
		}
		else {
			addToPaintBuffer(minglets);
		}
		
		for(int i = 0; i < paintBuffer.size(); i++) {
			Minglet a = (Minglet) paintBuffer.elementAt(i);
			paintMinglet(a);
		}
	}
	
	private void addToPaintBuffer(Hashtable t) {
		Enumeration e = t.elements();
		
		while(e.hasMoreElements()) {
			Minglet a = (Minglet) e.nextElement();
			addToPaintBuffer(a);
		}
	}
	
	private void addToPaintBuffer(Vector v) {
		for(int i = 0; i < v.size(); i++) {
			Minglet a = (Minglet) v.elementAt(i);
			addToPaintBuffer(a);
		}
	}
	
	private void addToPaintBuffer(Minglet a) {
		int j = 0;
		while(j < paintBuffer.size() && a.getY() > ((Minglet)paintBuffer.elementAt(j)).getY()) {
			j++;
		}
		paintBuffer.insertElementAt(a, j);
	}
	
	private void paintMinglet(Minglet a) {
		if(a == null) return;
		
		Graphics g = getGraphics();
		
		// We'd like to paint everything relative to the camera
		// Assume the camera's position is the center of the screen
		// If this is the case, we shouldn't paint the Minglet if its position
		// is > getWidth() / 2, since that is off-camera
		if(Math.abs(a.getX() - camera.getX()) > getWidth() / 2 + a.getWidth() / 2)
			return;
		
		// Make sure we aren't supposed to be falling in from the top
		if(newGuys.contains(a)) {
			a.paint(g, getWidth() / 2 + (a.getX() - camera.getX()), Math.min(fallOffset, a.getY()));
		}
		else if(!isTutorial) {
			// Say my name if we collide with the player
			// 		When: We are not the player, we collide with the player, and we are not mingling with the player
			if(a != player && a.collidesWith(player) && ! (a.getAIState() == Minglet.AI_MINGLING && a.getTarget() == player) ) {
				a.interrupt();
				
				// Don't paint this while the player is seeking a particular minglet
				if(!menuShowsCollisions && !menuShowsInhabitants || choiceMenu.getChoiceObject() == a) {
					g.setColor(0);
					SpeechBubble.paintString(g, a.getName(), JMingle.DEFAULT_FONT, getWidth() / 2 + (a.getX() - camera.getX()), a.getY() - a.getHeight());
					a.setAnimationState(Minglet.KEY_SMILE);
				}
			}
			
			// See if the player is spotting us from the "Browse" menu
			// When: We are not the player, one of the minglet-browswing menus is displayed, and the currently selected object is us
			else if(a != player && (menuShowsInhabitants || menuShowsCollisions) && choiceMenu.getChoiceObject() == a) {
				a.interrupt();
				SpeechBubble.paintString(g, a.getName(), JMingle.DEFAULT_FONT, getWidth() / 2 + (a.getX() - camera.getX()), a.getY() - a.getHeight());
				a.setAnimationState(Minglet.KEY_SMILE);
			}
			
			// If we are AI and we are mingling, show my mingle bubble
			// When: We are not the player and we are mingling  | OR | We are the player and the mingling flag is set
			else if( (a != player && a.getAIState() == Minglet.AI_MINGLING )
					|| (a == player) && a.getMingling()) {
				SpeechBubble.paintImage(g, tracker.getFavoriteThing(a.getAddress()), getWidth() / 2 + (a.getX() - camera.getX()), a.getY() - a.getHeight());
			}
			
			// If this guy is our friend (and we haven't mingled yet), show an indication to the player
			// When: we are not the player, we are a friend, and we have not been mingled with
			else if(a != player && a.isFriend() && !a.hasMingled()) {
				try {
					SpeechBubble.paintImage(g, ImageStore.getInstance().get("/res/graphics/smiley.png"), getWidth() / 2 + (a.getX() - camera.getX()), a.getY() - a.getHeight());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			// Remind the player to play if he ain't playing. That's what players do you know. They play. Hence
			// the origin of the word 'player'. One who plays.
			// When we are the player, we are idle, and we have not yet used the menu.
			else if(a == player && playerIsIdle() && !playerUsedMenu) {
				SpeechBubble.paintString(g, "Press select to look for minglets!", JMingle.DEFAULT_FONT, getWidth() / 2 + (a.getX() - camera.getX()), a.getY() - a.getHeight());
			}
			
			a.nextFrame();
			a.paint(g, getWidth() / 2 + (a.getX() - camera.getX()), a.getY());
		}
		else { // It's the tutorial.
			a.nextFrame();
			a.paint(g, getWidth() / 2 + (a.getX() - camera.getX()), a.getY());
		}
	}
	
	private void buildBackground() {
		buildBackground(inhabitants.size());
	}
	
	private void buildBackground(int population) {
		// Let there be ample space: 2 minglets per unit width
		int width = Math.max(getWidth(), 2 * population * player.getWidth());
		int height = getHeight();
		try {
			Image im = Image.createImage("/res/graphics/bg-tiles.png");
			
			int	xTiles = 1 + (int) Math.ceil( width / 16 );
			int	yTiles = 1 + (int) Math.ceil( height / 16 );
			BOUNDARY_TOP = BOUNDARY_BOTTOM -(16 * 2);
			BOUNDARY_RIGHT = (xTiles * 16);
			viewfinder = new LayerManager();
			viewfinder.setViewWindow(0, 0, getWidth(), getHeight());
			background = new TiledLayer(xTiles, yTiles, im, 16, 16);
			viewfinder.append(background);
			
			for(int i = 0; i < yTiles; i++) {
				for(int j = 0; j < xTiles; j++) {
					// Grass
					if(i > yTiles - 4)
						background.setCell(j, i, 7);
					// Grass tips
					else if(i == yTiles - 4)
						background.setCell(j, i, 6);
					// Sky
					else
						background.setCell(j, i, 5);
				}
			}
			
			// Sun
			background.setCell(xTiles - 3, 1, 1);
			background.setCell(xTiles - 2, 1, 2);
			background.setCell(xTiles - 3, 2, 3);
			background.setCell(xTiles - 2, 2, 4);
		
		}
		catch(IOException e) {
			
		}
	}
	
/* ------------------------------------------------------------------------------
 * 
 * Miscellaneous functions
 * 
 * ------------------------------------------------------------------------------ */
	
	private boolean playerIsIdle() {
		return (System.currentTimeMillis() - lastAction) > 1000 * 5;
	}
	
	private boolean playerIsColliding() {
		for(int i = 0; i < inhabitants.size(); i++) {
			Minglet a = (Minglet) inhabitants.elementAt(i);
			if(a.collidesWith(player)) return true;
		}
		return false;
	}
	
	public void construct() {
		String hash = bt.getMyMAC();
		String name = bt.getMyDeviceName();
		
		if(hash != null && name != null)
			player = Utils.makeMinglet(hash,name);
		else {
			player = null;
			return;
		}
		
		try {
			Image tmp = ImageStore.getInstance().get("/res/graphics/loading.png");
			loadingIcon = new Sprite(tmp, 18, 18);
			loadingIcon.setPosition(getWidth() / 2 - 9, getHeight() / 2 - tmp.getHeight() / 2 - 9);
		}
		catch (Exception e) {
			loadingIcon = null;
			e.printStackTrace();
		}
		
		camera.setInterpolationFactor(0.2);
		camera.setPosition(player.getX(), player.getY());
		
		monologueBox = new MonologueWidget( getWidth() / 4, getHeight() / 4, getWidth() / 2, 3, 3);
		monologueBox.setFont(JMingle.DEFAULT_FONT);
		
		tutorialBox = new MonologueWidget(getWidth()/16, 10, (14 * getWidth()) / 16, 3, 3);
		tutorialBox.setFont(JMingle.DEFAULT_FONT);
		
		idBox = new IDWidget(0, 0, getWidth(), player.getHeight() + 4 * getGraphics().getFont().getHeight(), tracker);
		toInitialState();
	}
	
	public void pause() {
		if(gameThread != null && gameThread.isAlive())
			gameThread.interrupt();
		gameThread = null;
		done = true;
	}
	
	public void start() {
		if(gameThread == null || !gameThread.isAlive()) {
			gameThread = new Thread(this);
			gameThread.start();
		}
		done = false;
		lastAction = System.currentTimeMillis();
	}
	
	public void toInitialState() {
		// If the player is null, stop!
		if(player == null) { return; }
		
		// There should be no other minglets at the start
		inhabitants.removeAllElements();
		
		// Build the background so we have appropriate resolution
		buildBackground();
		
		// Center the player
		player.setPosition(getWidth() / 2, (BOUNDARY_BOTTOM + BOUNDARY_TOP) / 2);
		
		// Center the camera on the player
		camera.setPosition(player.getX(), player.getY());
		
		// Destruct some stuff
		choiceMenu.clear();
		mingleSelection = null;
		
		idBox.setActive(false);
		monologueBox.setActive(false);
	}
	

	
	public void newArea() {
		if(!searching) {
			bt.doNewSearch(this);
			searching = true;
		} else {
			monologueBox.display("A scan is in progress!");
			monologueBox.setActive(true);
		}
	}
	
	public void stopScan() {
		if(searching) {
			bt.stopScan();
			searching = false;
		}
	}
	
	


	/**
	 * This method receives a vector lsit of Device's and interprets
	 * them into minglets. It is to be called by the BTHandler
	 * asynchronously.
	 * 
	 * It will:
	 * 1. Receive the new list
	 * 2. Keep the inhabitants that remain in the list
	 * 3. Put the remaining newly discovered minglets in the 'inhabitants' vector
	 * 4. Put the minglets who are no longer there in the 'exuent' vector
	 */
	public void receiveDeviceList(Vector v) {
		System.out.println("Now I'm done.");
		if(v == null){
			searching = false;
			return;
		}
		
		exuent.removeAllElements();
		newGuys.removeAllElements();
		
		buildBackground(v.size());
		
		// First thing: Pop the other guys over to 'exuent'
		for(int i = 0; i < inhabitants.size(); i++) {
			boolean isHere = false;
			Minglet a = (Minglet) inhabitants.elementAt(i);
			
			for(int j = 0; j < v.size(); j++) {
				Device d = (Device) v.elementAt(j);
				if(d.getID().equalsIgnoreCase(a.getAddress())) {
					isHere = true;
					break;
				}
			}
			
			if(!isHere) {
				inhabitants.removeElementAt(i);
				a.interrupt();
				a.setCollidable(false);
				i--;
				System.out.println(a.getAddress() + " left");
				exuent.addElement(a);
			}
		}
		
		// Next: loop through the new vector and add the new guys
		for(int i = 0; i < v.size(); i++) {
			Device d = (Device) v.elementAt(i);
			
			boolean flag = false;
			for(int j = 0; j < inhabitants.size(); j++) {
				Minglet a = (Minglet) inhabitants.elementAt(j);
				if(a.getAddress().equalsIgnoreCase(d.getID())) {
					flag = true;
					break;
				}
			}
			
			// Skip this guy if he's still in here.
			if(flag) {
				System.out.println(d.getID() + " remained");
				continue;
			}
			
			Minglet newGuy;
			if(tracker.know( d.getID() )) {
				try {
					long time = tracker.getRawLastSeenTime(d.getID());
					newGuy = Utils.makeMinglet(d.getID(), d.getName(),  time);
				}
				catch(Exception e) { // No such key
					newGuy = Utils.makeMinglet(d.getID(), d.getName());
				}
				
				newGuy.setFriend(true);
			}
			else { 
				System.out.println("We did not know " + d.getID());
				newGuy = Utils.makeMinglet(d.getID(), d.getName());
			}
			
			int x = BOUNDARY_LEFT + newGuy.getWidth() / 2 + JMingle.RANDOM.nextInt( (BOUNDARY_RIGHT - BOUNDARY_LEFT) - newGuy.getWidth() );
			int y = BOUNDARY_TOP + JMingle.RANDOM.nextInt( BOUNDARY_BOTTOM - BOUNDARY_TOP );
			
			fallVelocity = 0;
			fallOffset = BOUNDARY_TOP - getHeight() - JMingle.RANDOM.nextInt(BOUNDARY_BOTTOM - BOUNDARY_TOP); 
			
			System.out.println(d.getID() + " added");
			newGuy.setPosition(x, y);
			newGuy.setCollidable(false);
			newGuy.setAnimationState(Minglet.KEY_FALL);
			newGuys.addElement( newGuy );
		}
		
		searching = false;
	}
	
	public void setTutorial(boolean b) {
		isTutorial = b;
	}
	
	public void setTracker(MingleTracker t) {
		tracker = t;
	}
	
	public void setEventHandler(EventPipe e) {
		eventHandler = e;
	}
	
	public static Field constructField(MingleTracker mingles, EventPipe handler) {
		// Initialize some of the major components
		Field f = new Field(mingles);
		f.setEventHandler(handler);
		f.construct();
		return f;
	}
}
